from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth.models import *
from django.db import IntegrityError, DatabaseError
from django.utils import simplejson
from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm
from django.template import Template, RequestContext
from django.contrib.auth.models import User, check_password
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
#from django.contrib.auth import logout
from django.views.decorators.csrf import csrf_exempt
from django.forms.util import ErrorList
from django.forms import ValidationError
import re
from datetime import date, datetime

from time import sleep
import time

import logging

from dl4.dailylang.models import *
from dl4.dailylang.authz import *
from dl4.dailylang.forms import *

from django.core import serializers

logger = logging.getLogger(__name__)

#
# Is service available?
#

def is_service_available():
    parameter = AppParameters.objects.get(name='SERVICE_AVAILABLE')
    return (parameter.value == 'YES')
    
#
# JSON outputs - helper functions
#

def response_json(result, code, desc_en, desc_pl, info='', errors=None):
    my_dict = {'api': {
                   'response': {
                       result: {
                           'code': code,
                           'desc': {
                               'en': desc_en,
                               'pl': desc_pl,
                           },
                       },
                   },
               },
              }
              
    if len(info) > 0 or errors is not None:
        v = my_dict.get('api')
        v = v.get('response')
        v = v.get(result)
        if len(info) > 0:
            v['info'] = info
        if errors is not None:
            v['errors'] = errors
        
    json = simplejson.dumps(my_dict)
    return json
    
def r_json_error(code, desc_en, desc_pl, info='', errors=None):
    return response_json('error', code, desc_en, desc_pl, info, errors)

def r_json_success(code, desc_en, desc_pl, info=''):
    return response_json('success', code, desc_en, desc_pl, info)
    
#
# JSON outputs - registration
#

def service_unavailable():
    res = r_json_error(-3, 'Service unavailable.', 'Serwis niedostepny.') 
    return HttpResponse(res, mimetype="application/json")    

def bad_format():
    res = r_json_error(-3, 'Bad format.', 'Zly format.') 
    return HttpResponse(res, mimetype="application/json")    

def not_validated(errors=None):
    res = r_json_error(-3, 'Not validated.', 'Nie zwalidowano.', 'Zle dane', errors) 
    return HttpResponse(res, mimetype="application/json")  

def registration_ok():
    res = r_json_success(1, 'Registration OK.', 'Rejestracja powiodla sie.')         
    return HttpResponse(res, mimetype="application/json")

#
# JSON outputs - logging in and out
#

def bad_credentials():
    res = r_json_error(-7, 'Bad credentials.', 'Zle dane uwierzytelnienia.')         
    return HttpResponse(res, mimetype="application/json")
    res.status_code = 401
    res['WWW-Authenticate'] = 'Basic realm="%s"' % realm
    return HttpResponse(res, mimetype="application/json")
    
def login_ok():
    res = r_json_success(2, 'Logged in.', 'Zalogowano do serwisu.')         
    return HttpResponse(res, mimetype="application/json")

def logout_ok():
    res = r_json_success(2, 'Logged out.', 'Wylogowano z serwisu.')         
    return HttpResponse(res, mimetype="application/json")

#
# JSON outputs - change credentials 
#

def nick_not_found():
    res = r_json_error(-6, 'Nick not found.', 'Nie znaleziono nicka.') 
    return HttpResponse(res, mimetype="application/json")
    
def not_nick_owner():
    res = r_json_error(-7, 'Not nick owner.', 'Nie jest wlascicielka nicka.') 
    logger.debug('not_validated returns')
    return HttpResponse(res, mimetype="application/json")
    
def wrong_password():
    res = r_json_error(-7, 'Wrong password.', 'Nieprawidlowe haslo.') 
    return HttpResponse(res, mimetype="application/json")
    
def passwords_do_not_match():
    res = r_json_error(-7, 'Passwords do not match.', 'Nie zgadzaja sie hasla.')         
    return HttpResponse(res, mimetype="application/json")
    
def attempt_by_unauthenticated():
    res = r_json_error(-9, 'Attempt by unauthenticated.', 'Proba podjeta przez niezaautentykowana.')         
    return HttpResponse(res, mimetype="application/json")

def nick_already_registered():
    res = r_json_error(-7, 'Nick already registered.', 'Nick jest juz zarejestrowany.')         
    return HttpResponse(res, mimetype="application/json")
    
def credenials_change_error():
    res = r_json_error(-5, 'Problem changing credentials.', 'Problem przy zmianie danych uwierzytelnienia.') 
    return HttpResponse(res, mimetype="application/json")

def credentials_changed():
    res = r_json_success(2, 'Credentials changed.', 'Dane uwierzytelnienia zmienione.')         
    return HttpResponse(res, mimetype="application/json")

registration_errors = {
    "OK": "Registration OK",
    "SERVICE_PROBLEM": "Service problem during registration",
    "FORM_INVALID": "Invalid form POST'ed",
    "ALREADY_EXISTS": "User already exists in service"
}

logon_errors = {
    "OK": "Logged in",
    "SERVICE_PROBLEM": "Service problem during logon",
    "FORM_INVALID": "Invalid form POST'ed",
    "BAD_CREDENTIALS": "Bad username/password"
}

def registration_response(request, registration_error, logon_error=None):
    json = '{"registration_status": {"code": "' + registration_error + '", "desc": "' + registration_errors[registration_error] + '"}'
    if registration_error != 'OK':
        json += '}'
        return HttpResponse(json, mimetype="application/json")
    json += ', "logon_status": {"code": "' + logon_error + '", "desc": "' + logon_errors[logon_error] + '"}'
    if logon_error != 'OK':
        json += '}'
        return HttpResponse(json, mimetype="application/json")
    p = profile_content(request)
    json += ', "profile": ' + p + '}'
    return HttpResponse(json, mimetype="application/json")
    
def logon_response(request, logon_error):
    json = '{"logon_status": {"code": "' + logon_error + '", "desc": "' + logon_errors[logon_error] + '"}'
    if logon_error != 'OK':
        json += '}'
        return HttpResponse(json, mimetype="application/json")
    p = profile_content(request)
    json += ', "profile": ' + p + '}'
    return HttpResponse(json, mimetype="application/json")
    
#
# Views - registration
#
@csrf_exempt
@appkey_required('Application Key')
def register(request):
    if request.method == 'POST':
        logger.debug('register; method is POST')
        logger.debug('register; POST parameters are %s', request.POST)
        form = RegistrationForm(data=request.POST)
        logger.debug('register; is form valid?')
        if form.is_valid():  
            logger.debug('register; form is valid, attempting save()')
            try:
                #sleep(5)
                logger.debug('register; after first sleep')
                new_user = form.save()
                #sleep(5)
                logger.debug('register; after second sleep')
                #
                # Optional - log user in
                #
                cd = form.cleaned_data  
                logger.debug('register; form cleaned data are %s', cd)
                u = cd['username']
                p = cd['password1']
                logger.debug('register; authenticating user %s with password %s', u, p)
                user = auth.authenticate(username=u, password=p)
                if user is not None:
                    auth.login(request, user)
                    return registration_response(request, 'OK', 'OK')
                    #return logged_in(request)
                else:
                    return bad_credentials()
                #
                # End of optional log in
                #
                return registration_response(request, 'OK', 'OK')

                return registration_ok()
            except IntegrityError:
                logger.debug('register; IntegrityError')
                return nick_already_registered()
            except ValidationError:
                logger.debug('register; ValidationError')
                return nick_already_registered()
        else:  
            logger.debug('register; form is not valid')
            logger.debug('register; form errors are %s', form.errors)
            for key in form.errors:
                logger.debug('register; error list for %s is %s', key, form.errors[key].as_text)
                for e in form.errors[key]:
                    logger.debug('register; error %s', e)
            errors = dict([(k, form.error_class.as_text(v)) for k, v in form.errors.items()])
            logger.debug('register; error dictionary is %s', errors)
            return not_validated(errors)
            

    else:
        logger.debug('register; method is _not_ POST')
        form = RegistrationForm()
    #return render_to_response("registration/register.html", {'form': form})

    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("registration/register.html", csrfContext)

#
# Views - logging in and out
#
#@appkey_required('Application Key')
#@csrf_exempt
def login(request):
    logger.debug('login; cookies are %s' % request.COOKIES)
    if request.method == 'POST':
        logger.debug('login; method is POST')
        logger.debug('login; POST parameters are %s', request.POST)
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            logger.debug('login; form is valid')
            #sleep(5)
            cd = form.cleaned_data  
            u = cd['username']
            p = cd['password']
            logger.debug('login; authenticating user %s with password %s', u, p)
            user = auth.authenticate(username=u, password=p)
            #sleep(5)
            if user is not None:
                auth.login(request, user)
                return logon_response(request, 'OK')
                #return logged_in(request)
            else:
                return bad_credentials()
        else:
            logger.debug('login; form is not valid')
            errors = dict([(k, form.error_class.as_text(v)) for k, v in form.errors.items()])
            #sleep(10)
            return not_validated(errors)
    else:
        logger.debug('login; method is GET')
        form = AuthenticationForm()
    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("registration/login.html", csrfContext)

#@appkey_required('Application Key')
@login_required
def logged_in(request):
    return login_ok()
    
def logged_out(request):
    return logout_ok()
    
@appkey_required('Application Key')
@login_required
def logout(request, next_page):
    auth.logout(request)
    request.session['app_authenticated'] = True
    return logged_out(request)
    
#
# Views - change credentials
#

@appkey_required('Application Key')
@login_required
def change_credentials(request, current_username, username, password, password1, password2, email):
    if(username is None):
        return False
    logger.debug('change_credentials; current_username = %s', current_username)
    logger.debug('change_credentials; username = %s', username)
    logger.debug('change_credentials; password = %s', password)
    logger.debug('change_credentials; password1 = %s', password1)
    logger.debug('change_credentials; password2 = %s', password2)
    logger.debug('change_credentials; email = %s', email)
    username_change = len(username) != 0 and current_username != username
    logger.debug('change_credentials; is username to change? %s', username_change)

    # Is user logged on?
    #
    r_user = request.user.username
    logger.debug('change_credentials; r_user is %s', r_user)
    if not request.user.is_authenticated():
        return attempt_by_unauthenticated()
    
    # Is user changing her own credentials?
    #
    if(current_username != r_user):
        logger.debug('change_credentials; not username owner')
        return not_username_owner()
    
    # Do new passwords match?
    #
    if(password1 != password2):
        return passwords_do_not_match()
  
    # Get user from database
    #
    try:
        user = User.objects.get(username=current_username)
    except ObjectDoesNotExist:
        return username_not_found()
    logger.debug('change_credentials; user is %s', user)
    
    # Is current password correct?
    #
    if not user.check_password(password):
        return bad_credentials()

    # Modify User object
    #
    if username_change:
        user.username = username
        user.email = email
    user.set_password(password1)

    #sleep(15)
    
    # Try to save it
    #
    try:
        logger.debug('change_credentials; saving user %s', user)
        user.save()
    except DatabaseError, e:
        return credenials_change_error()
    except IntegrityError, e:
        return credenials_change_error()
        
    auth.logout(request)
    request.session['app_authenticated'] = True
    return credentials_changed()

@csrf_exempt
@appkey_required('Application Key')
@login_required
def credentials(request):
    if request.method == 'POST':
        logger.debug('credentials; request method is POST')
        form = NickForm(data=request.POST)
        if form.is_valid():
            logger.debug('credentials; form is valid')
            cd = form.cleaned_data            
            return change_credentials(
                request,
                cd['current_username'],
                cd['username'],
                cd['password'],
                cd['password1'],
                cd['password2'],
                cd.get('email', 'noreply@example.com'),
            )
        else:
            logger.debug('credentials; form is not valid')
            logger.debug('credentials; %s', form.errors)
            return not_validated()                 
    else:
        logger.debug('credentials; request method is not POST')
        form = NickForm()
        #return render_to_response('credentials_form.html', {'form': form})
    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("credentials_form.html", csrfContext)

@appkey_required('Application Key')
@login_required
def user_profile(request):
    if request.method == 'POST':
        logger.debug('user_profile')
        form = UserProfileForm(data=request.POST)
        if form.is_valid():
            logger.debug('user_profile; form is valid')
            form.save()
        else:
            logger.debug('user_profile; form is not valid')
            logger.debug('user_profile; %s', form.errors)
    else:
        logger.debug('user_profile; request method is not POST')
        form = UserProfileForm()
    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("registration/user_profile.html", csrfContext)

@appkey_required('Application Key')
@login_required
def user_change(request):
    if request.method == 'POST':
        logger.debug('user_change')
        form = UserChangeForm(data=request.POST)
        if form.is_valid():
            logger.debug('user_change; form is valid')
            form.save()
        else:
            logger.debug('user_change; form is not valid')
            logger.debug('user_change; %s', form.errors)
    else:
        logger.debug('user_change; request method is not POST')
        form = UserChangeForm()
    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("registration/user_change.html", csrfContext)

        
#
# Views - main page
#
        
@appkey_required('Application Key')
@login_required
def profile(request):
    return HttpResponseRedirect('/main/')

@appkey_required('Application Key')
@login_required
def main_page(request):
    logger.debug('main_page; user is %s', request.user)
    logger.debug('main_page; is user authenticated? %s', request.user.is_authenticated())
    
    return render_to_response('main-page.html', context_instance=RequestContext(request))
    
###############################################################################
#    Helper functions.
###############################################################################

def hello_from_dl(request):
    logger.debug('hello from DL')
    return HttpResponse("Hello world, from DL")

def digest_form(request):
    return render_to_response('digest_form.html')

def hexdigest(request):
    raw_password = request.GET['raw']
    salt = 'salt'
    hex_digest = get_hexdigest('sha1', salt, raw_password)
    return HttpResponse(hex_digest)

def get_appkey(request):
    return { 'current_appkey': request.GET['appkey'] }

###############################################################################
#    Data functions.
###############################################################################

@appkey_required('Application Key')
@login_required
def categories(request):
    logger.debug('categories;')
    c = Category.objects.all()
    data = serializers.serialize("json", c)
    logger.debug('categories; serialized data are %s', data)
    return HttpResponse(data, mimetype="application/json")

@login_required
def new_profile():
    categories = Category.objects.all()
    categories_ = serializers.serialize("json", categories)
    json = '{"categories": ' + categories_s + ', ' \
            +  '"user_categories: ' + [] + ', ' \
            +  '"phrases_of_day: ' + [] + ', ' \
            +  '"phrases: ' + [] + ', ' \
            +  '"translations: ' + [] + ', ' \
            + '}'
    return HttpResponse(json, mimetype="application/json")
    
def profile_content(request):
    logger.debug('profile;')
    username = request.user.username;
    try:
        user = User.objects.get(username=username)
    except ObjectDoesNotExist:
        return new_profile()
    logger.debug('profile; user is %s', user)

    user_profile = user.userprofile
    to_be_saved = False

    #
    # User categories
    #
    user_categories = user_profile.categories.all()
    if(user_categories.count() == 0):
        user_categories = Category.objects.filter(id__exact=1)
        user_profile.categories = user_categories
        to_be_saved = True
    user_categories_s = serializers.serialize("json", user_categories)

    #
    # User languages
    #
    user_languages = user_profile.languages.all()
    if(user_languages.count() == 0):
        user_languages = Language.objects.filter(code__in=['en-US', 'pl-PL'])
        user_profile.languages = user_languages
        to_be_saved = True
    user_languages_s = serializers.serialize("json", user_languages)

    if to_be_saved:
        user_profile.save()

    # 
    # User phrases of day
    #
    phrases_of_day = user_profile.phrases_of_day.all()
    phrases_of_day_s = serializers.serialize("json", phrases_of_day)

    #
    # All phrases from phrases of day ( PhraseOfDay --FK--> Phrase )
    # all translations of these phrases ( Translation --FK--> Phrase )
    #

    phrases = phrases_of_day.values_list('phrase')
    logger.debug('profile; phrases are %s', phrases)

    p = Phrase.objects.filter(id__in=phrases)
    phrases_s = serializers.serialize("json", p)
    logger.debug('profile; p = %s', p)

    t = Translation.objects.filter(phrase__id__in=phrases)
    translations_s = serializers.serialize("json", t)
    logger.debug('profile; t = %s', t)

    json = '{"categories": ' + user_categories_s + ', ' \
            +  '"languages": ' + user_languages_s + ', ' \
            +  '"phrases_of_day": ' + phrases_of_day_s + ', ' \
            +  '"phrases": ' + phrases_s + ', ' \
            +  '"translations": ' + translations_s \
            + '}'
    return json

@login_required
def profile(request):
    json = profile_content(request)
    return HttpResponse(json, mimetype="application/json")

def phrases_error(code):
    d = {'BAD_CATEGORIES_FORMAT': 'Bad format of Categories',
         'BAD_DAYS_FORMAT': 'Bad format of Days',
        }
    r = {'error': {'code': code, 'desc': d[code]}}
    json = simplejson.dumps(r)
    return HttpResponse(json, mimetype="application/json")

pattern_single = '^\d{8}$'
pattern_list = '^\d{8}(,\d{8})+$'
pattern_between = '^\d{8}-\d{8}$'

def date_from_day(hour=12):
    def f(day):
        yyyy = day[0:4]
        mm = day[4:6]
        dd = day[6:8]
    
        date = datetime.datetime(int(yyyy), int(mm), int(dd), hour, 0, 0)
        logger.debug('date_from_day; date is %s', date)

        return date
    return f

@login_required
def phrases_of_day(request):
    #
    # Are categories in GET?
    #
    if not 'categories' in request.GET:
        categories_list = Category.objects.all().values('categoryId')
        logger.debug('phrases_of_day; cagegories not found in GET')
        logger.debug('phrases_of_day; cagegories list is %s', categories_list)
    else:
        logger.debug('phrases_of_day; categories found in GET')
    
        #
        # Read categories from get
        #
        categories_commas = request.GET['categories']
        logger.debug('phrases_of_day; categories GET parameter is %s', categories_commas)
        
        #
        # Validate format 1,2,3,23
        #
        # TODO: regex \d+(,\d+)*
        if not re.search('^\d+(,\d+)*$', categories_commas):
            return phrases_error('BAD_CATEGORIES_FORMAT')
    
        categories_list = categories_commas.split(',')
        logger.debug('phrases_of_day; categories list is %s', categories_list)
        categories_list = map(int, categories_list)
        logger.debug('phrases_of_day; categories list is %s', categories_list)

    #
    # Is day in GET
    #
    if not 'day' in request.GET:
        logger.debug('phrases_of_day; cagegories not found in GET')
        date = datetime.date.today()
        d = date.isoformat() + " 12:00:00"
        logger.debug('phrases_of_day; d = %s', d)
        date = datetime.datetime(date.year, date.month, date.day, 12, 0, 0)
        phrases_of_day = PhraseOfDay.objects.filter(day=date, phrase__id__in=Phrase.objects.filter(category__id__in=categories_list))
    else:
        day = request.GET['day']
        date_str = str(day)

        if re.search(pattern_single, date_str):
            date = date_from_day()(date_str)
            logger.debug('phrases_of_day; single pattern, date is %s', date)
            phrase_list = Phrase.objects.filter(category__id__in=categories_list)
            logger.debug('phrases_of_day; querying PhraseOfDay where day = %s, phrase__id__in = %s', date, phrase_list)
            phrases_of_day = PhraseOfDay.objects.filter(day=date, phrase__id__in=Phrase.objects.filter(category__id__in=categories_list))

        elif re.search(pattern_list, date_str):
            date_list = map(date_from_day(12), date_str.split(','))
            logger.debug('phrases_of_day; list pattern, date list is %s', date_list)
            phrases_of_day = PhraseOfDay.objects.filter(day__in=date_list, phrase__id__in=Phrase.objects.filter(category__id__in=categories_list))

        elif re.search(pattern_between, date_str):
            date_list = map(date_from_day(), date_str.split('-'))
            logger.debug('phrases_of_day; range pattern, date list is %s', date_list)
            phrases_of_day = PhraseOfDay.objects.filter(day__gte=date_list[0], day__lt=date_list[1], phrase__id__in=Phrase.objects.filter(category__id__in=categories_list))
        else:
            return phrases_error('BAD_DAYS_FORMAT')

    logger.debug('phrases_of_day; phrases of day are %s', phrases_of_day)
    phrases_of_day_s = serializers.serialize("json", phrases_of_day)
    logger.debug('phrases_of_day; phrases_of_day_s = %s', phrases_of_day_s)

    phrase_ids = phrases_of_day.values_list('phrase')
    logger.debug('phrases_of_day; phrase IDs are %s', phrase_ids)

    phrases = Phrase.objects.filter(id__in=phrase_ids)
    logger.debug('phrases_of_day; phrases = %s', phrases)
    phrases_s = serializers.serialize("json", phrases)
    logger.debug('phrases_of_day; phrases_s = %s', phrases_s)

    translations = Translation.objects.filter(phrase__id__in=phrase_ids)
    logger.debug('phrases_of_day; translations = %s', translations)
    translations_s = serializers.serialize("json", translations)
    logger.debug('phrases_of_day; translations_s = %s', translations_s)
    json = '{' \
        +  '"categories": ' + str(categories_list) + ', ' \
        +  '"phrases_of_day": ' + phrases_of_day_s + ', ' \
        +  '"phrases": ' + phrases_s + ', ' \
        +  '"translations": ' + translations_s \
        + '}'
    return HttpResponse(json, mimetype="application/json")

def bad_post_phrases(code):
    d = {'NOT_A_POST': 'Only POST is accepted.',
         'INVALID_FORM': 'Form is invalid.',
        }
    r = {'error': {'code': code, 'desc': d[code]}}
    json = simplejson.dumps(r)
    return HttpResponse(json, mimetype="application/json")

@csrf_exempt
#@appkey_required('Application Key')
def add_phrases(request):
    logger.debug('add_phrases;')
    if request.method == 'POST':
        logger.debug('add_phrases; method is POST')
        logger.debug('add_phrases; POST parameters are %s', request.POST)
        form = AddPhrasesForm(data=request.POST)
        if form.is_valid():
            logger.debug('add_phrases; form is valid')
            #
            # Add Phrases of Day for User.
            #
            username = request.user.username
            user = User.objects.get(username=username)
            user_profile = user.userprofile

            pod_commas = request.POST['phrases_of_day']
            pod_list = pod_commas.split(',')
            pod_list = map(int, pod_list)
            logger.debug('add_phrases; pod_list = %s', pod_list)
            phrases_of_day = PhraseOfDay.objects.filter(id__in=pod_list)
            logger.debug('add_phrases; phrases of day are %s', phrases_of_day)

            for pod in phrases_of_day:
                logger.debug('add_phrases; adding phrase of day %s for user %s', pod, username)
                user_profile.phrases_of_day.add(pod)
                user_profile.save()

            #
            # Update Categories for User.
            #
            #
            # Read categories from get
            #
            categories_commas = request.POST['categories']
            logger.debug('add_phrases; categories POST parameter is %s', categories_commas)
            #
            # Validate format 1,2,3,23
            #
            # TODO: regex \d+(,\d+)*
        
            categories_list = categories_commas.split(',')
            logger.debug('add_phrases; categories list is %s', categories_list)
            categories_list = map(int, categories_list)
            logger.debug('add_phrases; categories list is %s', categories_list)

            user_profile.categories = categories_list
            user_profile.save()

            json = simplejson.dumps(request.POST)
            return HttpResponse(json, mimetype="application/json")
        else:
            logger.debug('add_phrases; form is not valid')
            return bad_post_phrases('INVALID_FORM')
    else:
        logger.debug('add_phrases; method is GET')
        form = AddPhrasesForm()
    initialData = {'form': form}
    csrfContext = RequestContext(request, initialData)
    return render_to_response("add_phrases.html", csrfContext)

